package main

import (
	"bytes"
	"encoding/base64"
	"encoding/binary"
	"fmt"
	"github.com/tendermint/tendermint/abci/example/code"
	"github.com/tendermint/tendermint/abci/types"
	"github.com/tendermint/tendermint/crypto/ed25519"
	cmn "github.com/tendermint/tendermint/libs/common"
	"github.com/tendermint/tendermint/libs/log"
	tmtypes "github.com/tendermint/tendermint/types"
	"github.com/tendermint/tendermint/version"
	dbm "github.com/tendermint/tm-db"
	"strconv"
	"strings"
)

var (
	AppVersion version.Protocol =0x1
	KVStoreDBName ="kvstore"
)

const (
	ValidatorSetChangePrefix string = "val:"  //验证人 前缀
)

const (
	CodeTypeOK            uint32 = 0
	CodeTypeEncodingError uint32 = 1
	CodeTypeBadNonce      uint32 = 2
	CodeTypeUnauthorized  uint32 = 3
	CodeTypeUnknownError  uint32 = 4
)


// 该 应用包含  验证人的 增 删 改 ，以及 KV存储
type KVStoreExApplication struct {
	types.BaseApplication

	state State

	ValUpdates []types.ValidatorUpdate

	valAddrToPubKeyMap map[string]types.PubKey  // {val_address:PubKey} key:为验证人地址

	logger log.Logger

}

var _ types.Application = (*KVStoreExApplication)(nil)


// dbDir : 数据库存储目录
func NewKVStoreExApplication(dbDir string) *KVStoreExApplication {

	db,err:=dbm.NewGoLevelDB(KVStoreDBName,dbDir)
	if err!=nil{
		panic(err)
	}
	state:=loadState(db)

	return &KVStoreExApplication{
		state:state,
		valAddrToPubKeyMap:make(map[string]types.PubKey),
		logger:log.NewNopLogger(),
	}
}


// 注意 如果你实现的区块链应用中有APPHASH 一定要保存 它们非常关键 否则
// 下次再启动的时候 重放之前的区块会出现异常的。
func (app *KVStoreExApplication) Info(req types.RequestInfo) types.ResponseInfo {
	fmt.Printf("=====Info\n")


	return types.ResponseInfo{
		Data:                 fmt.Sprintf("{\"size\":%v}",app.state.Size),
		Version:              version.ABCIVersion,
		AppVersion:           AppVersion.Uint64(),
		LastBlockHeight:      app.state.Height,
		LastBlockAppHash:     app.state.AppHash,

	}
}

func (app *KVStoreExApplication)  SetOption(req types.RequestSetOption) types.ResponseSetOption{
	fmt.Printf("=====SetOption\n")
	return app.SetOption(req)
}


// Save the validators in the merkle tree
func (app *KVStoreExApplication) InitChain(req types.RequestInitChain) types.ResponseInitChain {
	fmt.Printf("=====InitChain\n")

	fmt.Printf("Time:%s,ChainId:%s\n",req.Time.Format("2006-01-02 15:04:05"),req.ChainId)
	for _, v := range req.Validators {
		r:=app.updateValidator(v)
		if r.IsErr(){
			app.logger.Error("Error updating validators ","r",r)
		}

	}
	return types.ResponseInitChain{}
}



func (app *KVStoreExApplication) BeginBlock(req types.RequestBeginBlock) types.ResponseBeginBlock  {
	fmt.Printf("=====BeginBlock\n")

	// reset valset changes
	//  验证人重新 开始 新轮
	app.ValUpdates = make([]types.ValidatorUpdate, 0)

	for _,ev:= range req.ByzantineValidators{
		if ev.Type == tmtypes.ABCIEvidenceTypeDuplicateVote{
			// 多次投票 降低权重
			if ev.TotalVotingPower==0{
				continue
			}
			app.updateValidator(
				types.ValidatorUpdate{
					PubKey:              app.valAddrToPubKeyMap[string(ev.Validator.Address)],
					Power:                ev.TotalVotingPower-1,  //减去 1个数量权重

				})

		}
	}

	return types.ResponseBeginBlock{}
}


// tx is either "val:pubkey!power" or "key=value" or just arbitrary bytes
// 操作 包含修改验证人  和 设置数据
// 普通交易:  http://localhost:26657/broadcast_tx_commit?tx="k8=85"
// /更新验证人交易：  http://localhost:26657/broadcast_tx_commit?tx="val:Hm4ydlLGQl6AaSZoTe3is4HxtaxKkPgR9nZfwu1/4lo=!5"  val:公钥!power
func (app *KVStoreExApplication) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx  {
	fmt.Printf("=====DeliverTx")
	fmt.Printf("交易参数: %s",string(req.Tx))
	// if it starts with "val:", update the validator set
	// format is "val:pubkey!power"
	// 验证人操作
	if isValidatorTx(req.Tx) {
		// update validators in the merkle tree
		// and in app.ValUpdates
		return app.execValidatorTx(req.Tx)
	}

 // ======== KV 数据存储
	var key, value []byte
	parts := bytes.Split(req.Tx, []byte("="))
	if len(parts) == 2 {
		key, value = parts[0], parts[1]
	} else {
		key, value = req.Tx, req.Tx
	}

	app.state.db.Set(prefixKey(key), value)
	app.state.Size += 1

	events := []types.Event{
		{
			Type: "app",
			Attributes: []cmn.KVPair{
				{Key: []byte("creator"), Value: []byte("finder")},
				{Key: []byte("key"), Value: key},
			},
		},
	}

	return types.ResponseDeliverTx{Code: code.CodeTypeOK, Events: events}


}

func (app *KVStoreExApplication) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock  {

	return types.ResponseEndBlock{ValidatorUpdates:app.ValUpdates}
}

func (app *KVStoreExApplication) Commit() types.ResponseCommit  {
	fmt.Printf("===========Commit\n")
	appHash:=make([]byte,8)
	binary.PutVarint(appHash,app.state.Size) //这里把 大小作为Hash
	app.state.AppHash = appHash  // 应用状态修改
	app.state.Height+=1  // 区块高度修改,Height 其实并不是必须的，可以从data/priv_validator_state.json得到
	saveState(app.state)

	return types.ResponseCommit{Data:appHash}
}


// Returns an associated value or nil if missing.
// 查询格式1： http://localhost:26657/abci_query?path="/store"&data="k2"&height=3 字符串参数值要加双引号
// Path: 查询的时候才有
func (app *KVStoreExApplication)  Query(req types.RequestQuery) (res types.ResponseQuery) {
	fmt.Printf("===========Query\n")
	fmt.Printf("查询参数 %v",req.String())
	switch req.Path{
		case "/val":

			key:=[]byte("val:"+string(req.Data))

			// 验证人查询
			value:=app.state.db.Get(key)
			res.Key=req.Data
			res.Value=value
			return
		case "/store":
			return app.doQuery(req)
		default:
			return app.doQuery(req)
	}


}

// 做查询业务
func (app *KVStoreExApplication) doQuery(req types.RequestQuery) (res types.ResponseQuery) {
	if req.Prove{

		value:=app.state.db.Get(prefixKey(req.Data))
		res.Index = -1 // TODO make Proof return index
		res.Key = req.Data
		res.Value = value


		fmt.Printf("Value is %s ",value)

		if value != nil {
			res.Log = "exists"
		} else {
			res.Log = "does not exist"
		}

		return
	}else{
		res.Key = req.Data
		value := app.state.db.Get(prefixKey(req.Data))
		res.Value = value

		fmt.Printf("Value is %s ",value)
		if value != nil {
			res.Log = "exists"
		} else {
			res.Log = "does not exist"
		}
		return
	}

}


//CheckTx函数函数的作用是来决定是否将受到的交易放到交易池中。
// tendermint最初是将受到的交易先放到缓存区中的，
// 最终调用开发者实现的CheckTx函数来决定是否把在缓存区中的交易放到内存池中。
// 因为只有在内存池中的交易才会打包入区块。 这样做目的很明确，可以让应用层来决定此交易是否是合法的。
func (app *KVStoreExApplication) CheckTx(req types.RequestCheckTx)  types.ResponseCheckTx {
	fmt.Printf("=======CheckTx\n")
	return types.ResponseCheckTx{Code:CodeTypeOK,GasWanted:1}

}



// 添加、更新、删除 一个验证人
func (app *KVStoreExApplication) updateValidator(v types.ValidatorUpdate) types.ResponseDeliverTx {
	key:=[]byte("val:" +string(v.PubKey.Data))

	pubkey:=ed25519.PubKeyEd25519{}

	copy(pubkey[:],v.PubKey.Data)

	if v.Power ==0 {
		// 权重为0 需要移除
		if !app.state.db.Has(key){
			pubStr :=base64.StdEncoding.EncodeToString(v.PubKey.Data)

			return types.ResponseDeliverTx{
				Code:                 CodeTypeUnauthorized,
				Log:                 fmt.Sprintf("Cannot remove non-existent validator %s",pubStr),

			}
		}else{
			app.state.db.Delete(key)
			delete(app.valAddrToPubKeyMap,string(pubkey.Address()))
		}

	} else{
		// 权重大于0  做 添加或者修改验证人
		value := bytes.NewBuffer(make([]byte, 0))

		if err := types.WriteMessage(&v, value); err != nil {
			return types.ResponseDeliverTx{
				Code: code.CodeTypeEncodingError,
				Log:  fmt.Sprintf("Error encoding validator: %v", err)}
		}

		app.state.db.Set(key,value.Bytes())
		app.valAddrToPubKeyMap[string(pubkey.Address())]=v.PubKey


	}


	app.ValUpdates=append(app.ValUpdates,v)

	return types.ResponseDeliverTx{Code:CodeTypeOK}

}


// 验证交易数据
func isValidatorTx(tx []byte) bool {
	return strings.HasPrefix(string(tx), ValidatorSetChangePrefix)
}



// format is "val:pubkey!power"
// pubkey is a base64-encoded 32-byte ed25519 key
func (app *KVStoreExApplication) execValidatorTx(tx []byte) types.ResponseDeliverTx {

	tx=tx[len(ValidatorSetChangePrefix):] // pubkey!power

	pubKeyAndPower:=strings.Split(string(tx),"!")

	if len(pubKeyAndPower)!=2{
		return types.ResponseDeliverTx{
			Code:                 CodeTypeEncodingError,
			Log:                 fmt.Sprintf("Expected ‘pubkey!pwoer’ Got %v",pubKeyAndPower),

		}
	}
	pubKeyS,powerS:=pubKeyAndPower[0],pubKeyAndPower[1]


	pubkey,err:=base64.StdEncoding.DecodeString(pubKeyS)

	if err!=nil{
		return types.ResponseDeliverTx{
			Code: code.CodeTypeEncodingError,
			Log:  fmt.Sprintf("Pubkey (%s) is invalid base64", pubKeyS)}
	}

	// decode the power
	power, err := strconv.ParseInt(powerS, 10, 64)
	if err != nil {
		return types.ResponseDeliverTx{
			Code: code.CodeTypeEncodingError,
			Log:  fmt.Sprintf("Power (%s) is not an int", powerS)}
	}

	return app.updateValidator(types.Ed25519ValidatorUpdate(pubkey,power))

}
func prefixKey(key []byte) []byte {
	return append(kvPairPrefixKey, key...)
}
